package com.tao.anr

import android.os.Looper
import java.io.Serializable
import java.util.*

/**
 * Error thrown by [com.github.anrwatchdog.ANRWatchDog] when an ANR is detected.
 * Contains the stack trace of the frozen UI thread.
 *
 *
 * It is important to notice that, in an ANRError, all the "Caused by" are not really the cause
 * of the exception. Each "Caused by" is the stack trace of a running thread. Note that the main
 * thread always comes first.
 */
class ANRError private constructor(
    st: Ser._Thread?,
    /**
     * The minimum duration, in ms, for which the main thread has been blocked. May be more.
     */
    val duration: Long
) : Error("Application Not Responding for at least $duration ms.", st) {
    private class Ser(
        private val _name: String,
        private val _stackTrace: Array<StackTraceElement>
    ) : Serializable {
        inner class _Thread(other: _Thread?) : Throwable(_name, other) {
            override fun fillInStackTrace(): Throwable {
                stackTrace = _stackTrace
                return this
            }
        }
    }

    override fun fillInStackTrace(): Throwable {
        stackTrace = arrayOf()
        return this
    }

    companion object {
        private const val serialVersionUID = 1L
        fun New(duration: Long, prefix: String?, logThreadsWithoutStackTrace: Boolean): ANRError {
            val mainThread = Looper.getMainLooper().thread
            val stackTraces: MutableMap<Thread, Array<StackTraceElement>> = TreeMap(
                Comparator { lhs, rhs ->
                    if (lhs === rhs) return@Comparator 0
                    if (lhs === mainThread) return@Comparator 1
                    if (rhs === mainThread) -1 else rhs.name.compareTo(lhs.name)
                })
            for ((key, value) in Thread.getAllStackTraces()) if (key === mainThread
                || (key.name.startsWith(prefix!!)
                        && (logThreadsWithoutStackTrace
                        ||
                        value.size > 0))
            ) stackTraces[key] = value

            // Sometimes main is not returned in getAllStackTraces() - ensure that we list it
            if (!stackTraces.containsKey(mainThread)) {
                stackTraces[mainThread] = mainThread.stackTrace
            }
            var tst: Ser._Thread? = null
            for ((key, value) in stackTraces) tst = Ser(
                getThreadTitle(
                    key
                ), value
            )._Thread(tst)
            return ANRError(tst, duration)
        }

        fun NewMainOnly(duration: Long): ANRError {
            val mainThread = Looper.getMainLooper().thread
            val mainStackTrace = mainThread.stackTrace
            return ANRError(Ser(getThreadTitle(mainThread), mainStackTrace)._Thread(null), duration)
        }

        private fun getThreadTitle(thread: Thread): String {
            return thread.name + " (state = " + thread.state + ")"
        }
    }
}